/**
 * Copyright (C) 2009 Matteo Merli <mm@merlimat.org>
 * 
 * $Id: PxlObject.java 24 2009-01-19 19:49:05Z matteo.merli $
 * $URL: http://pxl-lang.googlecode.com/svn/trunk/src/main/java/pxl/types/PxlObject.java $
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *         http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package pxl.types;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import pxl.errors.AttributeError;
import pxl.errors.NotDefined;
import pxl.errors.TypeError;
import pxl.errors.UnsupportedOperand;
import pxl.types.annotations.Exported;
import pxl.types.util.PxlArgument;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * The Object interface is the base of all Pxl objects, data types, functions
 * and so on. All the methods are implemented with default fashioned
 * bvehaviours.
 * 
 * @author Matteo Merli, matteo.merli@gmail.com
 */
public abstract class PxlObject
{

    private Map<String, PxlObject> attrs = null;

    /**
     * Returns a string that represents the type of the object
     * 
     * @return type of the object
     */
    abstract public String getType();

    /**
     * Returns a string that represent the value of the object.
     * 
     * @return representation of the value
     */
    @Override
    public String toString()
    {
        return __repr__().getValue();
    }

    // Scope getScope();

    // // Operators
    @Exported
    public PxlString __repr__()
    {
        StringBuilder sb = new StringBuilder();
        sb.append( "<" ).append( getType() ).append( "@" ).append(
                this.hashCode() ).append( ">" );
        return new PxlString( sb.toString() );
    }

    @Exported
    public PxlString __str__()
    {
        return __repr__();
    }

    /**
     * Sum operator
     * 
     * @return the arithmetic sum of two objects
     */
    public PxlObject __add__( PxlObject other )
    {
        throw new UnsupportedOperand( "+", this, other );
    }

    /**
     * Subtraction operator
     * 
     * @return the arithmetic difference of two objects
     */
    public PxlObject __sub__( PxlObject other )
    {
        throw new UnsupportedOperand( "-", this, other );
    }

    /**
     * Multiplication operator
     * 
     * @return the arithmetic multiplication of two objects
     */
    public PxlObject __mul__( PxlObject other )
    {
        throw new UnsupportedOperand( "*", this, other );
    }

    /**
     * Division operator
     */
    public PxlObject __truediv__( PxlObject other )
    {
        throw new UnsupportedOperand( "/", this, other );
    }

    /**
     * Division operator
     */
    public PxlObject __floordiv__( PxlObject other )
    {
        throw new UnsupportedOperand( "//", this, other );
    }

    public PxlTuple __divmod__( PxlObject other )
    {
        throw new UnsupportedOperand( "divmod", this, other );
    }

    /**
     * Modulo operator
     */
    public PxlObject __mod__( PxlObject other )
    {
        throw new UnsupportedOperand( "%", this, other );
    }

    public PxlObject __iadd__( PxlObject other )
    {
        throw new UnsupportedOperand( "+=", this, other );
    }

    public PxlObject __isub__( PxlObject other )
    {
        throw new UnsupportedOperand( "-=", this, other );
    }

    /**
     * Should return the boolean value of the object, in the form of a
     * PxlBoolean.
     */
    public PxlBoolean __bool__()
    {
        throw new NotDefined( "__bool__", this );
    }

    public PxlObject __pos__()
    {
        throw new TypeError( "bad operand type for unary +: '" + getType()
                + "'" );
    }

    public PxlObject __neg__()
    {
        throw new TypeError( "bad operand type for unary -: '" + getType()
                + "'" );
    }

    public PxlObject __invert__()
    {
        throw new TypeError( "bad operand type for unary ~: '" + getType()
                + "'" );
    }

    public PxlObject __abs__()
    {
        throw new TypeError( "bad operand type for abs(): '" + getType() + "'" );
    }

    // Logical operations

    /**
     * Logical AND operator
     */
    public PxlObject __and__( PxlObject other )
    {
        return __bool__() == PxlBoolean.True ? other.__bool__()
                : PxlBoolean.False;
    }

    /**
     * Logical OR operator
     */
    public PxlObject __or__( PxlObject other )
    {
        return __bool__() == PxlBoolean.True ? PxlBoolean.True : other
                .__bool__();
    }

    /**
     * Logical NOT operator
     */
    public PxlObject __not__()
    {
        return __bool__() == PxlBoolean.True ? PxlBoolean.False
                : PxlBoolean.True;
    }

    public PxlObject __ior__( PxlObject other )
    {
        throw new UnsupportedOperand( "|", this, other );
    }

    public PxlObject __iand__( PxlObject other )
    {
        throw new UnsupportedOperand( "&", this, other );
    }

    public PxlObject __xor__( PxlObject other )
    {
        throw new UnsupportedOperand( "^", this, other );
    }

    public PxlObject __pow__( PxlObject other )
    {
        throw new UnsupportedOperand( "pow() or **", this, other );
    }

    public PxlObject __int__()
    {
        throw new TypeError(
                "int() argument must be a string or a number, not '"
                        + getType() + "'" );
    }

    public PxlObject __float__()
    {
        throw new TypeError(
                "float() argument must be a string or a number, not '"
                        + getType() + "'" );
    }

    // Comparison

    /**
     * Operator ==
     */
    public PxlBoolean __eq__( PxlObject other )
    {
        return PxlBoolean.valueOf( this == other );
    }

    /**
     * Operator !=
     */
    public PxlBoolean __neq__( PxlObject other )
    {
        return PxlBoolean.valueOf( this != other );
    }

    /**
     * Operator <
     */
    public PxlBoolean __lt__( PxlObject other )
    {
        throw new TypeError( "unorderable types: " + getType() + "() < "
                + other.getType() + "()" );
    }

    /**
     * Operator <=
     */
    public PxlBoolean __lte__( PxlObject other )
    {
        throw new TypeError( "unorderable types: " + getType() + "() <= "
                + other.getType() + "()" );
    }

    /**
     * Operator >
     */
    public PxlBoolean __gt__( PxlObject other )
    {
        throw new TypeError( "unorderable types: " + getType() + "() > "
                + other.getType() + "()" );
    }

    /**
     * Operator >=
     */
    public PxlBoolean __gte__( PxlObject other )
    {
        throw new TypeError( "unorderable types: " + getType() + "() >= "
                + other.getType() + "()" );
    }

    /**
     * Function call operator
     */
    public PxlObject __call__( List<PxlArgument> args )
    {
        throw new TypeError( "'" + getType() + "' object is not callable" );
    }

    /**
     * Constructor operator. This method is called when an object is
     * instantiated.
     */
    public PxlObject __new__( List<PxlObject> args )
    {
        throw new TypeError( "This object is not instantiable." );
    }

    /**
     * Subscript read operator. This is called when accessing a list or map-like
     * object for read. ex: x = f[ i ]
     */
    public PxlObject __getitem__( PxlObject key )
    {
        throw new TypeError( "Unsubscriptable object." );
    }

    /**
     * Subscript write operator. This is called when accessing a list or
     * map-like object for write. ex: f[ i ] = x
     */
    public void __setitem__( PxlObject key, PxlObject value )
    {
        throw new TypeError( "Unsubscriptable object." );
    }

    /**
     * Iterate operator. If the object is iterable, this method must be
     * implemented to return a PxlIterator object.
     */
    public PxlIterator __getiterator__()
    {
        throw new TypeError( "Object is not iterable." );
    }

    public PxlBoolean __contains__( PxlObject obj )
    {
        throw new TypeError( "Object is not iterable." );
    }

    /**
     * Return the length of an object. This could have different meanings for
     * differents kinds of object. Eg: the number of elements for a list, the
     * number of character for a string..
     */
    public PxlInteger __len__()
    {
        throw new TypeError( "len() of unsized object." );
    }

    public PxlObject __lshift__( PxlObject b )
    {
        throw new UnsupportedOperand( "<<", this, b );
    }

    public PxlObject __rshift__( PxlObject b )
    {
        throw new UnsupportedOperand( ">>", this, b );
    }

    // Attributes

    /**
     * 
     */
    public PxlObject __getattr__( String name )
    {
        PxlObject attr;

        // first look for a dinamic attribute
        if ( attrs != null )
        {
            attr = attrs.get( name );
            if ( attr != null )
            {
                if ( attr instanceof JavaMethod )
                    return new MemberMethod( this, (JavaMethod) attr );
                return attr;
            }
        }

        // search in static attributes
        attr = getAttribute( name );
        if ( attr == null )
            throw new AttributeError( this, name );

        if ( attr instanceof JavaMethod )
            return new MemberMethod( this, (JavaMethod) attr );
        return attr;
    }

    public void __setattr__( String name, PxlObject value )
    {
        if ( attrs == null )
            attrs = new ConcurrentHashMap<String, PxlObject>( 1 );

        attrs.put( name, value );
    }

    public PxlList __dir__()
    {
        Set<PxlString> set = new HashSet<PxlString>();

        for ( String attr : getAttributeNames() )
            set.add( new PxlString( attr ) );

        if ( attrs != null )
        {
            for ( String attr : attrs.keySet() )
                set.add( new PxlString( attr ) );
        }

        List<PxlString> list = Lists.sortedCopy( set );

        return new PxlList( list );
    }

    protected abstract PxlObject getAttribute( String name );

    protected abstract Collection<String> getAttributeNames();

    protected static Map<String, PxlObject> getExportedMethods(
            Class<? extends PxlObject> clazz )
    {
        Map<String, PxlObject> attrsMap = Maps.newTreeMap();

        Method[] methods = clazz.getMethods();
        for ( Method method : methods )
        {
            if ( method.isAnnotationPresent( Exported.class ) )
                attrsMap.put( method.getName(), new JavaMethod( method ) );
        }

        try
        {
            if ( clazz.equals( PxlInteger.class ) )
            {
                Method m = clazz.getMethod( "getType" );
                attrsMap.put( "__class__", new PxlString( clazz.newInstance()
                        .getType() ) );
            }
        }
        catch ( Exception e )
        {
            e.printStackTrace();
        }

        // This map are built at startup and are shared
        // between instances.
        return Collections.unmodifiableMap( attrsMap );
    }
}
